#include <v1model.p4>
#include <core.p4>

const bit<8> PROTOCOL_TCP = 0x06;
const bit<16> TYPE_NETWORK = 0x800; 

typedef bit<9> fabricPort_t;
typedef bit<48> hostAddress_t;
typedef bit<32> internetAddress_t;

header link_frame_t {
    hostAddress_t destination;
    hostAddress_t source;
    bit<16> protocol_id; 
}

header network_packet_t {
    bit<4> ip_version;
    bit<4> header_length;
    bit<6> differentiated_services;
    bit<2> explicit_congestion;
    bit<16> full_length;
    bit<16> pkt_identifier;
    bit<3> frag_flags;
    bit<13> offset_val;
    bit<8> hop_limit;
    bit<8> next_protocol;
    bit<16> header_check_sum;
    internetAddress_t source_addr;
    internetAddress_t destination_addr;
}

struct control_data {
    bit<19> threshold_value;
    bit<1> policy_applied;
}

struct processing_headers {
    link_frame_t link_frame;
    network_packet_t network_packet;
}

parser InputDecoder(packet_in stream,
                      out processing_headers parsed_hdrs,
                      inout control_data runtime_data,
                      inout standard_metadata_t system_metadata) {

    state initial_state { transition decode_link_frame; }

    state decode_link_frame {
        stream.extract(parsed_hdrs.link_frame);
        transition process_network_layer;
    }

    state process_network_layer {
        transition select(parsed_hdrs.link_frame.protocol_id) {
            TYPE_NETWORK: extract_network_packet;
            default: accept;
        }
    }

    state extract_network_packet {
        stream.extract(parsed_hdrs.network_packet);
        transition accept;
    }
}

control IntegrityCheck(inout processing_headers parsed_hdrs, inout control_data runtime_data) {
    apply {  }
}

control PipelineEntry(inout processing_headers parsed_hdrs,
                      inout control_data runtime_data,
                      inout standard_metadata_t system_metadata) {

    action configure_policy(bit<19> limit_value) {
        runtime_data.threshold_value = limit_value;
        runtime_data.policy_applied = 1;
    }

    table config_loader {
        key = {
            system_metadata.ingress_port: exact; 
        }
        actions = {
            configure_policy;
            NoAction;
        }
        size = 1;
        default_action = NoAction();
    }

    action l3_transit_operation(hostAddress_t new_mac, fabricPort_t output_port) {
        system_metadata.egress_spec = output_port;
        parsed_hdrs.link_frame.source = parsed_hdrs.link_frame.destination;
        parsed_hdrs.link_frame.destination = new_mac;
        parsed_hdrs.network_packet.hop_limit = parsed_hdrs.network_packet.hop_limit - 1;
    }

    action default_lpm_miss_action() {
        mark_to_drop(system_metadata);
    }
    
    table routing_decision {
        key = {
            parsed_hdrs.network_packet.destination_addr: lpm;
        }
        actions = {
            l3_transit_operation;
            default_lpm_miss_action;
        }
        size = 1024;
        default_action = default_lpm_miss_action();
    }

    apply {
        config_loader.apply();

        if (parsed_hdrs.network_packet.isValid()) {
            routing_decision.apply();
        }
    }
}

control PipelineExit(inout processing_headers parsed_hdrs,
                    inout control_data runtime_data,
                    inout standard_metadata_t system_metadata) {

    action set_congestion_point() {
        parsed_hdrs.network_packet.explicit_congestion = 3;  
    }

    table congestion_marker {
        key = {
            parsed_hdrs.network_packet.explicit_congestion: exact;
            system_metadata.enq_qdepth: range;
        }
        actions = {
            set_congestion_point;
            NoAction;
        }
        size = 64;
        default_action = NoAction();
    }

    apply {
        if (parsed_hdrs.network_packet.isValid()) {
            bit<2> ecn_field = parsed_hdrs.network_packet.explicit_congestion;
            
            if (ecn_field == 1 || ecn_field == 2) {
                congestion_marker.apply();
            }
        }
    }
}

control IntegrityUpdate(inout processing_headers parsed_hdrs, inout control_data runtime_data) {
    apply {
        update_checksum(
            parsed_hdrs.network_packet.isValid(),
            { parsed_hdrs.network_packet.ip_version,
              parsed_hdrs.network_packet.header_length,
              parsed_hdrs.network_packet.differentiated_services,
              parsed_hdrs.network_packet.explicit_congestion,
              parsed_hdrs.network_packet.full_length,
              parsed_hdrs.network_packet.pkt_identifier,
              parsed_hdrs.network_packet.frag_flags,
              parsed_hdrs.network_packet.offset_val,
              parsed_hdrs.network_packet.hop_limit,
              parsed_hdrs.network_packet.next_protocol,
              parsed_hdrs.network_packet.source_addr,
              parsed_hdrs.network_packet.destination_addr },
            parsed_hdrs.network_packet.header_check_sum,
            HashAlgorithm.csum16);
    }
}

control OutputEmitter(packet_out stream, in processing_headers parsed_hdrs) {
    apply {
        stream.emit(parsed_hdrs.link_frame);
        stream.emit(parsed_hdrs.network_packet);
    }
}

V1Switch(
InputDecoder(),
IntegrityCheck(),
PipelineEntry(),
PipelineExit(),
IntegrityUpdate(),
OutputEmitter()
) FlowProcessor;
